package com.gn.compile;

import com.gn.domain.MyPair;

import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.List;

/**
 *　　词法分析器负责的工作是从源代码里面读取文法符号，这是PL/0编译器的主要组成部分之一。
 */

public class Scanner {
    /**
     * 刚刚读入的字符
     */
    private char ch = ' ';
    /**
     * 添加，用于输出词法分析结果
     */
    public char preChar = ' ';

    /**
     * 当前读入的行
     * 用于输出错误的行将变量给为了public
     */
    public char[] line;

    /**
     * 当前行的长度（line length）
     */
    public int ll = 0;

    /**
     * 当前字符在当前行中的位置（character counter）
     */
    public int cc = 0;

    /**
     * 添加，当前字符的行数(line counter)
     */
    public int lc = 0;

    /**
     * 当前读入的符号
     */
    public Symbol sym;

    /**
     * 保留字列表（注意保留字的存放顺序）
     */
    public String[] word;//改成了公有，在输出词法分析结果时使用

    /**
     * 保留字对应的符号值
     */
    private final Symbol[] wsym;

    /**
     * 单字符的符号值
     */
    private final Symbol[] ssym;

    // 输入流
    private BufferedReader in;

    /**
     * 标识符名字（如果当前符号是标识符的话）
     * @see Parser
     * @see Table#enter
     */
    public String id;

    /**
     * 数值大小（如果当前符号是数字的话）
     * @see Parser
     * @see Table#enter
     */
    public int num;

    /**
     * 新增，用于标示词法分析的状态，如果分析完成，则为TRUE
     * 防止空指针异常
    */
    private boolean state;

    /**
     * 新增，存放String的代码
     */
    private String code;
    //截取字符串使用的变量
    private int begin = 0;
    private int end = 0;
    //private int codeLength;

    /**
     * 新增用于标示文件结束
     */
    private boolean finish;

    /**
     * 初始化词法分析器
     * @param input PL/0 源文件输入流
     */
    //#构造函数，在主函数中传入源文件输入流fin
    public Scanner(BufferedReader input) {
        in = input;

        // 设置单字符符号
        //#Symbol自定义枚举类型，创建枚举类型的数组，大小为256
        ssym = new Symbol[256];
        java.util.Arrays.fill(ssym, Symbol.nul);//#compile.Symbol.nul不能识别的符号，使用这个符号初始化枚举数组。
        //#ASCLL码表一共127个，创建了大小为256的枚举数组，将数组中对应某个符号的ascll码位置上的枚举设置为这个符号
        ssym['+'] = Symbol.plus;
        ssym['-'] = Symbol.minus;
        ssym['*'] = Symbol.times;
        ssym['/'] = Symbol.slash;
        ssym['('] = Symbol.lparen;
        ssym[')'] = Symbol.rparen;
        ssym['='] = Symbol.eql;
        ssym[','] = Symbol.comma;
        ssym['.'] = Symbol.period;
        ssym['#'] = Symbol.neq;
        ssym[';'] = Symbol.semicolon;
        ssym['%'] = Symbol.percent;
        ssym['!'] = Symbol.exclam;

        // 设置保留字名字,按照字母顺序，便于折半查找
        word = new String[] {"begin", "call", "const", "do", "end", "if",
                "odd", "procedure", "read", "then", "var", "while", "write"};

        // 设置保留字符号
        //#compile.PL0.norw为关键字个数
        //#创建的关键字的枚举数组，但是这次有几个关键字就出创建了多大，关键字在数组中按照字母顺序连续放置。
        wsym = new Symbol[PL0.norw];
        wsym[0] = Symbol.beginsym;
        wsym[1] = Symbol.callsym;
        wsym[2] = Symbol.constsym;
        wsym[3] = Symbol.dosym;
        wsym[4] = Symbol.endsym;
        wsym[5] = Symbol.ifsym;
        wsym[6] = Symbol.oddsym;
        wsym[7] = Symbol.procsym;
        wsym[8] = Symbol.readsym;
        wsym[9] = Symbol.thensym;
        wsym[10] = Symbol.varsym;
        wsym[11] = Symbol.whilesym;
        wsym[12] = Symbol.writesym;
    }


    /**
     * 新添加，重载构造函数，接受String的源代码输入
     * 初始化词法分析器
     * @param input PL/0 源文件String
     */
    public Scanner(String input) {
        code = input;
        //同时将代码长度初始化,begin,end变量自动初始化为零
        //codeLength = code.length();

        // 设置单字符符号
        //#Symbol自定义枚举类型，创建枚举类型的数组，大小为256
        ssym = new Symbol[256];
        java.util.Arrays.fill(ssym, Symbol.nul);//#compile.Symbol.nul不能识别的符号，使用这个符号初始化枚举数组。
        //#ASCLL码表一共127个，创建了大小为256的枚举数组，将数组中对应某个符号的ascll码位置上的枚举设置为这个符号
        ssym['+'] = Symbol.plus;
        ssym['-'] = Symbol.minus;
        ssym['*'] = Symbol.times;
        ssym['/'] = Symbol.slash;
        ssym['('] = Symbol.lparen;
        ssym[')'] = Symbol.rparen;
        ssym['='] = Symbol.eql;
        ssym[','] = Symbol.comma;
        ssym['.'] = Symbol.period;
        ssym['#'] = Symbol.neq;
        ssym[';'] = Symbol.semicolon;
        ssym['%'] = Symbol.percent;
        ssym['!'] = Symbol.exclam;
        ssym[':'] = Symbol.colon;
        // 设置保留字名字,按照字母顺序，便于折半查找
        word = new String[]{"begin", "call", "const", "do", "else", "end", "for","if", "odd", "procedure",
                "read", "repeat", "sqrt", "then", "to", "until", "var", "while", "write"};

        // 设置保留字符号
        //#compile.PL0.norw为关键字个数
        //#创建的关键字的枚举数组，但是这次有几个关键字就出创建了多大，关键字在数组中按照字母顺序连续放置。
        wsym = new Symbol[PL0.norw];
        wsym[0] = Symbol.beginsym;
        wsym[1] = Symbol.callsym;
        wsym[2] = Symbol.constsym;
        wsym[3] = Symbol.dosym;
        wsym[4] = Symbol.elsesym;
        wsym[5] = Symbol.endsym;
        wsym[6] = Symbol.forsym;
        wsym[7] = Symbol.ifsym;
        wsym[8] = Symbol.oddsym;
        wsym[9] = Symbol.procsym;
        wsym[10] = Symbol.readsym;
        wsym[11] = Symbol.repeat;
        wsym[12] = Symbol.sqrt;
        wsym[13] = Symbol.thensym;
        wsym[14] = Symbol.tosym;
        wsym[15] = Symbol.until;
        wsym[16] = Symbol.varsym;
        wsym[17] = Symbol.whilesym;
        wsym[18] = Symbol.writesym;
    }

    /**
     * 后添加
     * 从String中读取一个字符，每次读取一行,这不是为减少磁盘I/O次数了，是为了报错时给出行数
     */
    void getch() {
        String l = "";//#读取的一行
        try {
            //#cc当前字符在当前行中的位置（character counter）
            //#ll当前行的长度（line length）两个变量的初始值都为0
            if (cc == ll) { //# 当cc和ll相等的时候就开始重新获取一行
                while (l.equals("")) {
                    //# toLowerCase使用默认语言环境的规则将此String所有字符转换为小写。
                    //# readLine读取不包含终止字符
                    l = getLine().toLowerCase() + "\n";
                    lc ++;
                }
                ll = l.length();
                cc = 0; //# 更新cc的值，从重新获取的一行的开头取字符。
                //# line当前读入的行的字符串char[]
                line = l.toCharArray(); //# 保存在成员变量中的不是获取的String，而是char[],便于索引。
                //System.out.println(compile.PL0.interp.cx + " " + l);
                PL0.fa1.println(PL0.interp.cx + " " + l);

            }
        } catch (Exception e) {
            //# 原为imcomplete,应该是拼错了，改为incomplete
            throw new Error("program incomplete");//#Error new一个java.Error的无名对象，在堆中，抛出异常。
        }
        ch = line[cc]; //# 取字符
        cc ++; //#cc永远指向下一个需要获取的字符
        //# 没有返回值，在调用getch函数之后需要从ch中取得新获取的字符。
    }

    /**
     * 为了接收String类型的源代码输入，书写的获取一行的函数
     */
    public String getLine() {
        //从开始处开始搜索，找到\n标记位end
        end = code.indexOf('\n', begin);
        if (end == -1) { //#最后一行是如果没有换行符，则直接截取到最后
            if (!finish)
            {
                finish = true;
                return code.substring(begin);//substring(begin,end)中begin=end什么都截取不到，前闭后开
            }
            else
            {
                Err.report(9);
                return ".";
            }
        }
        String subCode = code.substring(begin,end);
        //下一次的begin应该是end+1
        begin = end + 1;

        return subCode;
    }

    /**
     * 词法分析，获取一个词法符号，是词法分析器的重点
     */
    public void getsym() {
        // Wirth 的 PL/0 编译器使用一系列的if...else...来处理
        // 但是你的助教认为下面的写法能够更加清楚地看出这个函数的处理逻辑
        while (Character.isWhitespace(ch)) {	// 跳过所有空白字符
            getch();
        }
        if (ch >= 'a' && ch <= 'z' || ch == '_') {
           //# 先读取一个有效字符，然后进行判断，如果是字母（因为都转成小写了，不用考虑大写字母），
            //# 则交给matchKeywordOrIdentifier()处理，此函数会继续读取接下来的字母。
            // 关键字或者一般标识符
            matchKeywordOrIdentifier();
        }
        else if (ch >= '0' && ch <= '9') {
           //# 同样，如果读入的一个字符为数字，剩下的交给matchNumber处理。
            // 数字
            matchNumber();
        }
        else {
            // 操作符
            matchOperator();
        }
        System.out.println(sym);
    }


    /**
     * 分析关键字或者一般标识符
     */
    void matchKeywordOrIdentifier() {
        //# 匹配关键字或者是标示符
        int i;
        //# al符号的最大长度
        //# 创建一个字符串构造器，容量为符号的最大长度
        StringBuilder sb = new StringBuilder(PL0.al);
        // 首先把整个单词读出来
        do {
            sb.append(ch); //# 先将调用它的函数所读取的一个字母添加到字符串构造器中，然后再继续读取。
            getch();
        }
        while (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_'); //# 直到读取的当前字符不是数字也不是字母

        //# id标识符名字（如果当前符号是标识符的话）String类型
        id = sb.toString(); //# 应该是重写的object中的toString方法

        // 然后搜索是不是保留字（请注意使用的是什么搜索方法）
        //# 使用的是二分查找，java.util.Arrays类中包含用于操作数组的各种方法（例如排序和搜索），返回的是元素在数组中的索引
        i = java.util.Arrays.binarySearch(word, id);

        // 最后形成符号信息
        if (i < 0) {
            //# 不是关键字就是标识符
            //# 如果没有在关键字数组中找到，则将读入的符号类型设置为标识符
            // 一般标识符
            //# sym当前读入的符号，枚举类型Symbol
            sym = Symbol.ident;
        }
        else {
            // 关键字
            //# 如果找到了，则设置sym为对应的关键字，关键字一字一类，所有标识符算一类。
            sym = wsym[i];
        }
    }

    /**
     * 分析数字
     */
    void matchNumber() {
        int k = 0;  //# 计数读入的是几位的数字
        //# sym当前读入的符号，枚举类型Symbol，所有数字算一类
        sym = Symbol.number;
        //# num数值大小（如果当前符号是数字的话）
        num = 0;
        do {
            //# 每新读入一个数字字符，就执行一遍乘十再加上新读入字符的数值的操作
            //# digit() 返回指定基数中指定字符的数值，10为设置的基数
            num = 10*num + Character.digit(ch, 10);
            k ++;
            getch();
        }
        while (ch>='0' && ch<='9'); 				// 获取数字的值
        k --; //# 最后多加一个，再减去
        if (k > PL0.nmax) {
            //# nmax number的最大位数
            Err.report(30);
        }
    }

    /**
     * 分析操作符
     */
    void matchOperator()
    {
        // 请注意这里的写法跟Wirth的有点不同
        switch (ch) {
            case ':':		// 赋值符号 :=
                getch();
                if (ch == '=') {
                    sym = Symbol.becomes;
                    getch(); //# 将要识别的符号读取完整，再往下读一个字符
                }
                else {
                    // 不能识别的符号
                    sym = Symbol.colon;
                    //# 新增，数组界限
                }
                break;
            case '<':		// 小于或者小于等于
                getch();
                if (ch == '=') {
                    sym = Symbol.leq;
                    getch();  //# 再往下读一个字符
                }
                else {
                    sym = Symbol.lss;
                    //# 除去“=“ 已经往下读取一个了，不用再读了
                }
                break;
            case '>':		// 大于或者大于等于
                getch();
                if (ch == '=') {
                    sym = Symbol.geq;
                    getch();
                }
                else {
                    sym = Symbol.gtr;
                }
                break;
            case '+':
                getch();
                if (ch == '=')
                {
                    sym = Symbol.pluseql;
                    getch();
                }
                else if (ch == '+')
                {
                    sym = Symbol.incinc;
                    getch();
                }
                else
                {
                    sym = Symbol.plus;
                }
                break;
            case '-':
                getch();
                if(ch=='=')  /*‘-’后面是‘=’就把minueql赋给sym */
                {
                    sym=Symbol.minueql;
                    getch();
                }
                else  if(ch=='-')   /*‘-’后面是‘-’就把decdec赋给sym*/
                {
                    sym=Symbol.decdec;
                    getch();
                }
                else
                {
                    sym=Symbol.minus;
                }
                break;
            case '*':
                getch();
                if(ch=='=')/*‘*’后面是‘=’就把timeseql赋给sym */
                {
                    sym = Symbol.timeseql;
                    getch();
                }
                else
                {
                    sym = Symbol.times;
                }
                break;
            case '/': /*如果字符是‘/’  */
                getch();
                if(ch=='=')  /* ‘/’后面是‘=’把slasheql赋给sym  */
                {
                    sym = Symbol.slasheql;
                    getch();
                }
                else
                {
                    sym = Symbol.slash;
                }
                break;
            case '{':
                do
                {
                    getch();
                }
                while (ch != '}');
                getch();
                getsym();
                break;

            default:		// 其他为单字符操作符（如果符号非法则返回nul）
                sym = ssym[ch];
                //# 双字操作符需要继续往下读取一个做判断，其他单字操作（包括非法符号）符直接使用枚举数组来赋值。
                //# 为什么要用256的数组，关键在于这，一个字符是一个字节，有可能出现0-255的任何一个数
                if (sym != Symbol.period)  //# 如果不是PL/0语言的最终结束符号”.“，就继续往下读取一个字符
                {
                    preChar = ch;
                    getch();
                }
                break;
        }
    }
    /*/**
     * 词法分析输出二元式函数
     * 进行词法分析，调用一遍PL0.lex.getScannerOutString()函数，分析一个单词，返回一个字符串（一行）
     * 分析完成后在调用返回字符串finish，词法分析出错返回如error19,17，表示错误在第19行，17列

    public String getScannerOutString() {
        StringBuilder out = new StringBuilder();
        Formatter fmt = new Formatter(out);
        String operator;
        if(!state) {
            getsym();
            if (sym == Symbol.ident) { //标识符
                fmt.format("%-18s%-12s", "1标识符" + id, "(1,'" + id + "')");
            } else if (sym == Symbol.number) { //常数
                fmt.format("%-18s%-12s", "2常数" + num, "(2,'" + num + "')");
            } else if (sym.ordinal() > Symbol.becomes.ordinal() &&
                    sym.ordinal() <= Symbol.procsym.ordinal()) { // 关键字
                fmt.format("%-18s%-12s", "3关键字" + id, "(3,'" + id + "')");
            } else if (sym.ordinal() > Symbol.number.ordinal() && sym.ordinal() <= Symbol.geq.ordinal() ||
                    sym == Symbol.becomes) { //运算符
                switch (sym) {
                    case becomes:
                        operator = ":=";
                        break;
                    case leq:
                        operator = "<=";
                        break;
                    case lss:
                        operator = "<";
                        break;
                    case geq:
                        operator = ">=";
                        break;
                    case gtr:
                        operator = ">";
                        break;
                    default:
                        operator = Character.toString(preChar);
                }
                fmt.format("%-18s%-12s", "4运算符" + operator, "(4,'" + operator + "')");
            }
            else if (sym.ordinal() > Symbol.becomes.ordinal() && sym.ordinal() <= Symbol.colon.ordinal()) { //界符
                //如果分析到结束符，设置状态为TRUE，再调用此函数返回finish
                if (sym == Symbol.period) {
                    state = true;
                    fmt.format("%-18s%-12s", "5界符" + '.', "(5,'" + '.' + "')");
                }
                else {
                    fmt.format("%-18s%-12s", "5界符" + preChar, "(5,'" + preChar + "')");
                }
            }
            else {
                return "error" + lc + "," + (cc-1);
            }
            return out.toString();
        }
        else {
            return "finish";
        }
    }*/

    /**
     * 词法分析输出二元式函数
     * 返回二元组（Integer,String）第一个Integer表示单词编码，第二个为值
     * 进行词法分析，调用一遍PL0.lex.getScannerOut()函数，分析一个单词
     * 分析完成后在调用返回(7，"finish")
     * 词法分析出错返回如(6,"19,17")，表示错误在第19行，17列
     * 单词编码：1：标识符，2：常数，3：关键字，4：运算符，5：界符，6：error，7：finish
     */
    public MyPair<Integer, String> getScannerOut()
    {
        MyPair<Integer, String> tuples = new MyPair<>();
        String operator;
        if (!state) {
            getsym();
            if (sym == Symbol.ident) {//标识符
                //fmt.format("%-18s%-12s", "1标识符" + id, "(1,'" + id + "')");
                tuples.setFirst(1);
                tuples.setSecond(id);
            }
            else if (sym == Symbol.number) { //常数
                //fmt.format("%-18s%-12s", "2常数" + num, "(2,'" + num + "')");
                tuples.setFirst(2);
                tuples.setSecond(String.valueOf(num));
            }
            else if (sym.ordinal() > Symbol.colon.ordinal() && sym.ordinal() <= Symbol.tosym.ordinal()) {  // 关键字
                //fmt.format("%-18s%-12s", "3关键字" + id, "(3,'" + id + "')");
                tuples.setFirst(3);
                tuples.setSecond(id);
            }
            else if (sym.ordinal() > Symbol.number.ordinal() && sym.ordinal() <= Symbol.becomes.ordinal())
            { //运算符
                switch (sym) {
                    case plus:
                        operator = "+";
                        break;
                    case minus:
                        operator = "-";
                        break;
                    case times:
                        operator = "*";
                        break;
                    case slash:
                        operator = "/";
                        break;
                    case percent:
                        operator = "%";
                        break;
                    case exclam:
                        operator = "!";
                        break;
                    case eql:
                        operator = "=";
                        break;
                    case neq:
                        operator = "#";
                        break;
                    case becomes:
                        operator = ":=";
                        break;
                    case leq:
                        operator = "<=";
                        break;
                    case lss:
                        operator = "<";
                        break;
                    case geq:
                        operator = ">=";
                        break;
                    case gtr:
                        operator = ">";
                        break;
                    case incinc:
                        operator = "++";
                        break;
                    case decdec:
                        operator = "--";
                        break;
                    case sqrt:
                        operator = "sqrt";
                        break;
                    case pluseql:
                        operator = "+=";
                        break;
                    case minueql:
                        operator = "-=";
                        break;
                    case timeseql:
                        operator = "*=";
                        break;
                    case slasheql:
                        operator = "/=";
                        break;
                    case oddsym:
                        operator = "odd";
                        break;
                    default:
                        operator = Character.toString(preChar);
                }
                //fmt.format("%-18s%-12s", "4运算符" + operator, "(4,'" + operator + "')");
                tuples.setFirst(4);
                tuples.setSecond(operator);
            }
            else if (sym.ordinal() > Symbol.becomes.ordinal() && sym.ordinal() <= Symbol.colon.ordinal()) {  //界符
                //如果分析到结束符，设置状态为TRUE，再调用此函数返回finish
                if (sym == Symbol.period) {
                    state = true;
                    //fmt.format("%-18s%-12s", "5界符" + '.', "(5,'" + '.' + "')");
                    tuples.setFirst(5);
                    tuples.setSecond(".");
                }
                else {
                    switch (sym)
                    {
                        case lparen:
                            operator = "(";
                            break;
                        case rparen:
                            operator = ")";
                            break;
                        case comma:
                            operator = ",";
                            break;
                        case semicolon:
                            operator = ";";
                            break;
                        case colon:
                            operator = ":";
                            break;
                        default:
                            operator = Character.toString(preChar);
                    }
                    //fmt.format("%-18s%-12s", "5界符" + preChar, "(5,'" + preChar + "')");
                    tuples.setFirst(5);
                    tuples.setSecond(operator);
                }
            }
            else {
                //return "error" + String.valueOf(lc) + "," + String.valueOf(cc-1);
                tuples.setFirst(6);
                tuples.setSecond(lc + "," + (cc-1));
            }
            return tuples;
        }
        else {
            tuples.setFirst(7);
            tuples.setSecond("finish");
            return tuples;
        }
    }

    /**
     *getScannerOuts的一次全部输出的形式
     * @return List<pair<Integer,String>>
     */
    public List<MyPair<Integer, String> > getScannerOuts()
    {
        //#首先重置使用到的数据成员，防止词法分析多次调用
        reset();
        List<MyPair<Integer, String> > scannerOuts = new ArrayList<>();
        MyPair<Integer, String> scannerOut;
        do {
            scannerOut = getScannerOut();
            scannerOuts.add(scannerOut);
        }
        while (scannerOut.first != 7);
        return scannerOuts;
    }

    public void reset()
    {
        begin = 0;
        end = 0;
        ch = ' ';
        preChar = ' ';
        ll = 0;
        cc = 0;
        lc = 0;
        state = false;
    }
}
